/* PluginsEngine.vala
 *
 * Copyright (C) 2008  Raphael Bosshard
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 * Author:
 * 	Raphael Bosshard <raphael.bosshard@gmail.com>
 */

using GLib;


public class Appendage.PluginsEngine <T> : Object {

	public string plugin_group_name 		{ public get; public set; }
	public string plugin_configuration_file_suffix	{ public get; public set; }

	private List <File>				plugins_directories ;
	private List <PluginInfo <T>>	plugins_list;
	
	private List <PluginInfo <T>>	enabled_plugins_list;

	construct {
		plugin_configuration_file_suffix = PLUGIN_CONFIGURATION_FILE_SUFFIX;
		plugin_group_name = PLUGIN_GROUP_NAME;
	
		plugins_directories = new List<File> ();
		plugins_list = new List <PluginInfo <T>> ();		
		enabled_plugins_list = new List <PluginInfo <T>> ();
	}

	/**
	* Will read the infomation from a single .plugin configuration file.
	*/
	private PluginInfo <T> load (string file_path) throws ConfigurationError {
		PluginInfo <T> info = new PluginInfo <T> ();

		string key = "Version";

		GLib.KeyFile plugin_configuration_file = new GLib.KeyFile();
	
		GLib.debug("Loading plugin configuration from path '%s'", file_path);
		
		info.file = file_path;
	
		if (!plugin_configuration_file.load_from_file (file_path, GLib.KeyFileFlags.NONE)) {
			GLib.warning ("Configuration for plugin '%s' could not be loaded", file_path );
			throw new ConfigurationError.NOT_FOUND ("Configuration for plugin '%s' could not be loaded", file_path );
		}
	
		if(!plugin_configuration_file.has_key (plugin_group_name, key)){
			GLib.warning("Plugin configuration file carries no version information. Might be mandatory in future versions.");	
		}

		if(plugin_configuration_file.get_integer (plugin_group_name, key) != 1){
			GLib.critical ("Configuration file version information does not match: 1.0 expected");
			throw new ConfigurationError.NOT_VALID ("Configuration file version information does not match: 1.0 expected");
		}
	
		try {			
			key = "Module";
			info.location = plugin_configuration_file.get_string (plugin_group_name, key);

			//key = "Loader";			
			//info.loader = plugin_configuration_file.get_string(plugin_group_name, "Loader");

			key = "Name";
			info.name = plugin_configuration_file.get_string (plugin_group_name, key);

			key = "Description";
			info.description = plugin_configuration_file.get_string (plugin_group_name, key);

			key = "Icon"; 
			info.icon_name = plugin_configuration_file.get_string (plugin_group_name, key);

			key = "Authors";
			info.authors = plugin_configuration_file.get_string_list (plugin_group_name, key);

			key = "Copyright";
			info.copyright = plugin_configuration_file.get_string (plugin_group_name, key);

			key = "Website";
			info.website = plugin_configuration_file.get_string (plugin_group_name, key);

		} catch (KeyFileError e) {
			GLib.error ("Couldn't find key '%s': ignoring plugin '%s'", key, file_path);
			throw new ConfigurationError.NOT_FOUND ("Couldn't find key '%s': ignoring plugin '%s'", key, file_path);
			
		}
		return info;
	}

	/**
	* Reads a directory, selects all '.plugin' files (at least by default) and loads the plugin information
	*/
	private void load_dir (File plugin_directory) throws GLib.Error {

		FileEnumerator children;
	
		GLib.debug("Loading directory: '%s'", plugin_directory.get_path ());
	
		//plugin_directory = File.new_for_path(directory_path);
	
		FileInfo info = plugin_directory.query_info ("standard::*", FileQueryInfoFlags.NONE , null);
	
		if (info.get_file_type () != FileType.DIRECTORY) {
			GLib.warning ("Could not open plugins directory");
			throw new IOError.NOT_DIRECTORY ("Location '%s' does not point to a directory", plugin_directory.get_path ());
		}
	
		children = plugin_directory.enumerate_children ("standard::*", FileQueryInfoFlags.NONE, null);
	
		while (null != (info = children.next_file (null))) {
			PluginInfo plugin_info;

	
			if (! (info.get_display_name ()).has_suffix (this.plugin_configuration_file_suffix)) {
				continue;
			}
		
			string plugin_path = Path.build_filename (plugin_directory.get_path (), info.get_display_name ());
		
		
			try {
				plugin_info = this.load (plugin_path);
			
			} catch (ConfigurationError e) {
				GLib.warning ("Could not load plugin '%s':\n\t%s", plugin_path, e.message );
				continue;
			}
		
			/* If a plugin with this name has already been loaded
			 * drop this one (user plugins override system plugins) */	
			if (null != plugins_list.find_custom (plugin_info, (CompareFunc) plugins_compare)) {
				GLib.warning("Two or more plugins named '%s'. Only the first will be considered.", plugin_info.location);
				continue;
			}
			
			GLib.debug ("Plugin '%s' loaded", plugin_info.name);
			plugins_list.append (plugin_info); 
		
		}
	}

	/**
	* Reads all the .plugin files from the provided plugin directories
	*/
	public void load_all () {
		if (0 == plugins_directories.length ()) {
			GLib.warning ("No plugins directories set");
		}

		foreach (File directory in plugins_directories) {
			try {
				this.load_dir (directory);
			} catch (Error e) {
				GLib.warning ("Could not load directory '%s': %s ", directory.get_path (), e.message );
			}
		}
	}

	/**
	* Add a directory to the list of plugin directories
	*/
	public void add_plugins_directory (File directory) {
		try {
			FileInfo info = directory.query_info ("standard::*", FileQueryInfoFlags.NONE , null);

			if (info.get_file_type () != FileType.DIRECTORY) {
				GLib.warning ("Could not open plugins directory");
				return;
			}
		
			plugins_directories.append (directory);
		
		} catch (GLib.Error e) {
		
			GLib.warning (	"There was an error while reading file information for directory '%s': \n\t%s",
					directory.get_path (), e.message );
		
		}
	}

/*
//	'if (object in list)' doesn't work yet.

	public void enable_plugins ( List<string> plugins_locations ) {
		foreach (PluginInfo plugin_info in this.plugins_list) {
			GLib.debug ("Looking for plugin '%s' to enable", plugin_info.location );
		
			if (plugin_info.location in plugins_locations ) {
				try {
					this.enable_plugin (plugin_info);
				} catch (ModuleError e) {
					GLib.error ("Could not enable plugin '%s':\n\t%s", plugin_info.location, e.message);
				}				
			} else {
				GLib.debug ("Plugin '%s' was not found", plugin_info.location );				
			}
		}
	}

	public void disable_plugins (List<string> plugins_locations) {
		foreach (PluginInfo plugin_info in this.plugins_list) {
			GLib.debug ("Looking for plugin '%s' to disable", plugin_info.location );
		
			if (plugin_info.location in plugins_locations) {				
				try {
					this.disable_plugin (plugin_info);
				} catch (ModuleError e) {
					GLib.error ("Could not disable plugin '%s':\n\t%s", plugin_info.location, e.message);
				}
			} else {
				GLib.debug ("Plugin '%s' was not found", plugin_info.location );							
			}
		}
	}
*/


	public void enable_all () {
		foreach (PluginInfo plugin_info in this.plugins_list) { 
	 		try {
				this.enable_plugin (plugin_info);
			
			} catch (ModuleError e) {
				GLib.warning ("Could not enable plugin '%s':\n\t%s", plugin_info.location, e.message);
				continue;
			}
 		}
	}
	
	private void enable_plugin (PluginInfo plugin_info) throws ModuleError, ModuleLoaderError {
		GLib.debug ("Trying to enable plugin '%s'", plugin_info.location);
		
		plugin_info.enable ();
		this.enabled_plugins_list.append (plugin_info);	
		
		GLib.debug ("Plugin '%s' enabled", plugin_info.location);	
	}
	
	public void disable_all () {
		foreach (PluginInfo plugin_info in this.enabled_plugins_list) {
			try {
				this.disable_plugin (plugin_info);
			} catch (ModuleError e) {
				GLib.warning ("Could not disable plugin '%s':\n\t%s", plugin_info.location, e.message);
			}
		}
	}
	
	private void disable_plugin (PluginInfo plugin_info) throws ModuleError {
		GLib.debug ("Trying to disable plugin '%s'", plugin_info.location);
		
		plugin_info.disable ();
		this.enabled_plugins_list.remove (plugin_info);				
		
		GLib.debug ("Plugin '%s' disabled", plugin_info.location);
	}
	

	
	public weak GLib.List <PluginInfo> get_plugins () {
		return plugins_list;
	}
	
	public weak GLib.List <PluginInfo> get_plugins_enabled () {
		return enabled_plugins_list;
	}
	
	~ PluginsEngine () {
	
	}
	
	/*
	public static int main(string[] argv) {
		return 0;
	}
	*/	
}

